﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Xml;
using System.ComponentModel;

/**
 * @file GameObject
 * 
 * @author LeonXie
 * */

namespace Catsland.Core {

    /**
     * @brief GameObject
     * 
     * the CatComponents can be added to GameObject
     * */
    public class GameObject : Drawable {
        // unique id for a gameObject
        public string _guid = "";
        // name of gameObject, not need to be unique
        private string _name = "UntitledGameObject";
        [CategoryAttribute("Basic")]
        public string Name {
            set {
                _name = value;
                if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                    Mgr<GameEngine>.Singleton.Editor.GameObjectNameChanged();
                }
            }
            get { return _name; }
        }
        /**
         * the position of the gameObject in game world(3d).
         * the game world(3d) position will be mapped to screen 2d space according to
         * view angle(set in Scene).
         * position of the gameObject, in-game world space, will be scaled by view point
         */
        // the xy position in game world
        private Vector2 _position;
        [CategoryAttribute("Location")]
        public Vector2 Position {
            set { _position = value; }
            get { return _position; }
        }
        // the z position in game world, will be scaled by view point
        private float _height;
        [CategoryAttribute("Location")]
        public float Height {
            set { _height = value; }
            get { return _height; }
        }

        private Dictionary<string, CatComponent> _components;
        // debug box
        private VertexPositionColor[] _vertex;
        private VertexBuffer _vertexBuffer;

        public GameObject() {
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                _vertex = new VertexPositionColor[4];
                _vertex[0] = new VertexPositionColor(new Vector3(-0.1f, 0.0f, 0.0f), Color.White);
                _vertex[1] = new VertexPositionColor(new Vector3(0.1f, 0.0f, 0.0f), Color.White);
                _vertex[2] = new VertexPositionColor(new Vector3(0.0f, 0.1f, 0.0f), Color.White);
                _vertex[3] = new VertexPositionColor(new Vector3(0.0f, -0.1f, 0.0f), Color.White);

                _vertexBuffer = new VertexBuffer(Mgr<GraphicsDevice>.Singleton, typeof(VertexPositionColor),
                    4, BufferUsage.None);
                _vertexBuffer.SetData<VertexPositionColor>(_vertex);
            }

            // assign a random id
            _guid = Guid.NewGuid().ToString();
        }

        /**
         * @brief initialize gameObject and its components
         * 
         * @param scene the scene
         * 
         * @return success?
         * */
        public bool Initialize(Scene scene) {
            // initialize all components
            if (_components != null) {
                foreach (KeyValuePair<string, CatComponent> key_value in _components) {
                    key_value.Value.Initialize(scene);
                }
            }
            return true;
        }

        /**
         * @brief update every component in game mode
         * 
         * @param lastTimeFrame the time interval since last frame
         * */
        public void Update(int lastTimeFrame) {
            if (_components != null) {
                foreach (KeyValuePair<string, CatComponent> key_value in _components) {
                    key_value.Value.Update(lastTimeFrame);
                }
            }
        }

        /**
         * @brief update every component in editor mode
         * 
         * @param lastTimeFrame the time interval since last frame
         * */
        public void EditorUpdate(int lastTimeFrame) {
            if (_components != null) {
                foreach (KeyValuePair<string, CatComponent> key_value in _components) {
                    key_value.Value.EditorUpdate(lastTimeFrame);
                }
            }
        }

        /**
         * @brief draw the debug box
         * 
         * @param timeLastFrame
         * */
        void Drawable.Draw(int timeLastFrame) {
            // debug information
            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                BasicEffect effect = Mgr<DebugTools>.Singleton.DrawEffect;
                effect.View = Mgr<Camera>.Singleton.m_view;
                effect.Projection = Mgr<Camera>.Singleton.m_projection;
                effect.VertexColorEnabled = false;
                effect.World = Matrix.CreateTranslation(
                    _position.X, _position.Y * Mgr<Scene>.Singleton._yCos, 0.0f);
                effect.DiffuseColor = new Vector3(0.3f, 0.0f, 0.0f);

                Mgr<GraphicsDevice>.Singleton.SetVertexBuffer(_vertexBuffer);
                foreach (EffectPass pass in effect.CurrentTechnique.Passes) {
                    pass.Apply();
                    Mgr<GraphicsDevice>.Singleton.DrawUserPrimitives<VertexPositionColor>(
                        PrimitiveType.LineList,
                        _vertex,
                        0,
                        2);
                }
            }
        }

        public override string ToString() {
            return Name;
        }

        /**
         * @brief hard copy this gameObject and its components
         * */
        public GameObject CloneGameObject() {
            GameObject gameObject = new GameObject();
            gameObject.Name = new String(_name.ToCharArray());
            gameObject.Position = new Vector2(_position.X, _position.Y);
            gameObject.Height = _height;
            // guid should not be cloned

            // Components
            foreach (KeyValuePair<string, CatComponent> keyValue in _components) {
                gameObject.AddComponent(keyValue.Key, keyValue.Value.CloneComponent(gameObject));
            }
            return gameObject;
        }

        public bool SaveToNode(XmlNode node, XmlDocument doc) {
            XmlElement gameObject = doc.CreateElement("GameObject");
            node.AppendChild(gameObject);

            // name and guid
            gameObject.SetAttribute("name", _name);
            gameObject.SetAttribute("guid", _guid);

            // position
            XmlElement position = doc.CreateElement("Position");
            gameObject.AppendChild(position);
            position.SetAttribute("X", "" + _position.X);
            position.SetAttribute("Y", "" + _position.Y);
            position.SetAttribute("Z", "" + _height);

            // components
            XmlElement components = doc.CreateElement("Components");
            gameObject.AppendChild(components);

            if (_components != null) {
                foreach (KeyValuePair<string, CatComponent> keyValue in _components) {
                    keyValue.Value.SaveToNode(components, doc);
                }
            }

            return true;
        }

        public static GameObject LoadFromNode(XmlNode node, Scene scene) {
            XmlElement gameObject = (XmlElement)node;

            // name and guid
            GameObject newGameObject = new GameObject();
            String name = gameObject.GetAttribute("name");
            String guid = gameObject.GetAttribute("guid");
            newGameObject._name = name;
            newGameObject._guid = guid;

            // location
            XmlElement position = (XmlElement)gameObject.SelectSingleNode("Position");
            Vector2 postionXY = new Vector2(float.Parse(position.GetAttribute("X")),
                                        float.Parse(position.GetAttribute("Y")));
            float positionHeight = float.Parse(position.GetAttribute("Z"));
            newGameObject._position = postionXY;
            newGameObject._height = positionHeight;

            // components
            XmlNode components = gameObject.SelectSingleNode("Components");
            foreach (XmlNode component_node in components.ChildNodes) {
                string component_name = component_node.Name;
                CatComponent component = CatComponent.LoadFromNode((XmlElement)component_node, scene, newGameObject);
                newGameObject.AddComponent(component.GetType().Name, component);
            }
            return newGameObject;
        }

        /**
         * @brief destroy this gameObject and its components
         * */
        public void Destroy() {
            if (_components != null) {
                foreach (KeyValuePair<string, CatComponent> keyValue in _components) {
                    keyValue.Value.Destroy();
                }
            }

            if (Mgr<GameEngine>.Singleton._gameEngineMode == GameEngine.GameEngineMode.MapEditor) {
                Mgr<Scene>.Singleton._debugDrawableList.RemoveItem(this);
            }
        }

        public float GetDepth() {
            // TODO: maybe set Height as a factor is more suitable?
            return _position.Y;
        }

        /**
         * @brief used to sort gameObject to perform Painter Algorithm
         * 
         * @param obj compare to other gameObject
         * 
         * @result -1 deeper 0 equal 1 shallow
         * */
        public int CompareTo(object obj) {
            float otherDepth = ((Drawable)obj).GetDepth();
            float thisDepth = GetDepth();
            if (otherDepth > thisDepth) {
                return 1;
            }
            else if (otherDepth < thisDepth) {
                return -1;
            }
            return 0;
        }

        
        /**
         * the following functions are about components.
         * consider to replace them with uniqueList
         * */
        #region
        /**
         * @brief add components
         * 
         * @param component_name name of the component
         * @param component the component
         * */
        public void AddComponent(string component_name, CatComponent component) {
            if (_components == null) {
                _components = new Dictionary<string, CatComponent>();
            }
            _components.Add(component_name, component);
        }

        public Dictionary<string, CatComponent> GetComponentList() {
            return _components;
        }

        public CatComponent GetComponent(string component_name) {
            if (_components == null) {
                return null;
            }
            else {
                if (_components.ContainsKey(component_name)) {
                    return _components[component_name];
                }
                return null;
            }
        }

        public bool RemoveComponent(string component_name) {
            if (_components == null) {
                return false;
            }
            if (_components.ContainsKey(component_name)) {
                CatComponent catComponent = _components[component_name];
                _components.Remove(component_name);
                catComponent.Destroy();
                return true;
            }
            return false;
        }
        #endregion
    }
}
